package com.misboot.document.aspect.log;


import cn.ewsd.common.bean.Audience;
import cn.ewsd.common.utils.CookieUtil;
import cn.ewsd.common.utils.JwtUtil;
import cn.ewsd.common.utils.StringUtils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.misboot.document.client.SystemClient;
import com.misboot.document.constants.RequestLogType;
import io.jsonwebtoken.Claims;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;


/**
 * 修改方面
 *
 * @Author 湖南佐佑时代科技有限公司（https://www.zuoyo.com）
 * @Version V5.4.21
 * @Email service@zuoyo.com
 * @Date 2023/03/24
 */
@Aspect
@Component
@Order(1)
public class OperateLogAspect {

    private final static Logger logger = LoggerFactory.getLogger(OperateLogAspect.class);

    @Autowired
    private Audience audience;

    @Autowired
    private SystemClient systemClient;

    /**
     * 排除字段
     */
    public static final String[] EXCLUDE_PROPERTIES = {"modifyTime"};


    @Pointcut("execution(public * com.misboot.report.controller.*.*(..))")
    public void webLog() {
    }

    @Around("@annotation(enableModifyLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperateLog enableModifyLog) throws Throwable {
        //存储修改前的对象
        Map<String, Object> oldMap = new HashMap<>();
        if (ModifyName.UPDATE.equals(enableModifyLog.operationType())) {
            try {
                ContentParser contentParser = (ContentParser) SpringUtil.getBean(enableModifyLog.parseClass());
                Object oldObject = contentParser.getOldResult(joinPoint, enableModifyLog);
                if (enableModifyLog.needDefaultCompare()) {
                    oldMap = (Map<String, Object>) objectToMap(oldObject); //存储修改前的对象
                }
            } catch (Exception e) {
                logger.error("service加载失败:", e);
            }
        }
        //执行service
        long startTime = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        //记录耗时(毫秒)
        long time = System.currentTimeMillis() - startTime;
        //记录修改具体字段信息
        String str = "";
        if (ModifyName.UPDATE.equals(enableModifyLog.operationType())) {
            ContentParser contentParser;
            Object parserOldResult = null;
            try {
                String responseParams = JSON.toJSONString(proceed);
                contentParser = (ContentParser) SpringUtil.getBean(enableModifyLog.parseClass());
                parserOldResult = contentParser.getOldResult(joinPoint, enableModifyLog);
            } catch (Exception e) {
                logger.error("service加载失败:", e);
            }
            // 默认不进行比较，可以自己在logService中自定义实现，降低对性能的影响
            if (enableModifyLog.needDefaultCompare()) {
                str = defaultDealUpdate(parserOldResult, oldMap);
            }
        } else {
            String responseParams = JSON.toJSONString(proceed);
        }
        // 保存当前日志到数据库中
        saveSysOperationLog(null, joinPoint, time, proceed, str);
        return proceed;
    }

    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     *
     * @param joinPoint 切入点
     * @param e         异常信息
     */
    @AfterThrowing(pointcut = "webLog() ", throwing = "e")
    public void saveServiceExceptionLog(JoinPoint joinPoint, Throwable e) {
        saveSysOperationLog(joinPoint, null, 0, null,
                stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
    }

    private void saveSysOperationLog(JoinPoint joinPoints, ProceedingJoinPoint joinPoint, long time, Object result, String str) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        try {
            Signature signature = ObjectUtil.isEmpty(joinPoints) ? joinPoint.getSignature() : joinPoints.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            OperateLog requestLogAnnotation = method.getAnnotation(OperateLog.class);
            if (null != requestLogAnnotation) {
                Map<String, Object> map = new HashMap<>();
                Claims var12 = JwtUtil.parseJWT(getToken(request), this.audience.getBase64Secret());
                if (ObjectUtil.isEmpty(var12)) {
                    return;
                }
                map.put("operationDesc", requestLogAnnotation.handleName());
                map.put("userNameId", Convert.toStr(var12.get("userNameId")));
                map.put("userName", Convert.toStr(var12.get("userName")));
                map.put("operationIp", ServletUtil.getClientIP(request));
                map.put("operationUserAgent", request.getHeader("User-Agent"));
                map.put("operationUrl", Convert.toStr(request.getRequestURL()));
                map.put("operationMethodName", ObjectUtil.isEmpty(joinPoints) ?
                        joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()
                        : joinPoints.getSignature().getDeclaringTypeName() + "." + joinPoints.getSignature().getName());
                map.put("operationMethod", request.getMethod());
                map.put("operationRequestArgs", ObjectUtil.isEmpty(joinPoints) ? Convert.toStr(joinPoint.getArgs()) : getParams(joinPoints));
                map.put("operationTime", time + "ms");
                map.put("operationType", ObjectUtil.isEmpty(result) ? RequestLogType.ERROR_SERVER_NAME : RequestLogType.SUCCESS_SERVER_NAME);
                map.put("operationResponeArgs", Convert.toStr(JSONObject.toJSON(result)));
                map.put("operationContent", StrUtil.isNotBlank(str) ? str : Convert.toStr(JSONObject.toJSON(result)));
                map.put("operationModular", requestLogAnnotation.operationModular());
                systemClient.save(map);
                // 发布事件保存数据库 map不会触发
//                SpringUtils.context().publishEvent(map);
            }
        } catch (Exception exception) {
            logger.error("记录日志失败={}", exception.getMessage());
        }
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        return message;
    }

    private String defaultDealUpdate(Object newObject, Map<String, Object> oldMap) {
        try {
            Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
            StringBuilder str = new StringBuilder();
            Object finalNewObject = newObject;
            oldMap.forEach((k, v) -> {
                Object newResult = newMap.get(k);
                if (v != null && !v.equals(newResult)) {
                    Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
                    //排除字段
                    if (!Arrays.asList(EXCLUDE_PROPERTIES).contains(field.getName())) {
                        FieldMeta dataName = field.getAnnotation(FieldMeta.class);
                        if (dataName != null) {
                            str.append("【").append(dataName.name()).append("】从【")
                                    .append(v).append("】改为了【").append(newResult).append("】;\n");
                        } else {
                            str.append("【").append(field.getName()).append("】从【")
                                    .append(v).append("】改为了【").append(newResult).append("】;\n");
                        }
                    }

                }

            });
            return str.toString();

        } catch (Exception e) {
            logger.error("比较异常", e);
            throw new RuntimeException("比较异常", e);
        }
    }

    private Map<?, ?> objectToMap(Object obj) {
        if (obj == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        Map<?, ?> mappedObject = mapper.convertValue(obj, Map.class);

        return mappedObject;
    }

    /**
     * 获取Token
     */
    private String getToken(HttpServletRequest request) {
        String var7 = CookieUtil.getCookieByName(request, "token");
        String paramToken = request.getParameter("token");
        if (!StringUtils.isNullOrEmpty(paramToken)) {
            var7 = paramToken;
        }
        if (StringUtils.isNullOrEmpty(var7)) {
            var7 = request.getHeader("token");
        }
        return var7;
    }

    //请求参数
    private String getParams(JoinPoint joinPoint) {
        String params = "";
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                Object arg = joinPoint.getArgs()[i];
                if ((arg instanceof HttpServletResponse) || (arg instanceof HttpServletRequest)
                        || (arg instanceof MultipartFile) || (arg instanceof MultipartFile[])) {
                    continue;
                }
                try {
                    params += JSONObject.toJSONString(joinPoint.getArgs()[i]);
                } catch (Exception e1) {

                }
            }
        }
        return params;
    }

}
